/*
 * WebServer.c
 *   socket实现web服务（遵循http协议）
 *
 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#include<sys/stat.h>
#include <sys/sendfile.h>

#include "substr.h"

#define EOL "\r\n"
#define EOL_SIZE 2

typedef struct {
        char *ext;
        char *mediatype;
} extn;

//可能请求的文件类型
extn extensions[] = {
        {"gif", "image/gif"},
        {"txt", "text/plain"},
        {"jpg", "image/jpg"},
        {"jpeg", "image/jpeg"},
        {"png", "image/png"},
        {"ico", "image/ico"},
        {"zip", "image/zip"},
        {"gz", "image/gz"},
        {"tar", "image/tar"},
        {"htm", "text/html"},
        {"html", "text/html"},
        {"php", "text/html"},
        {"pdf", "application/pdf"},
        {"zip", "application/octet-stream"},
        {"rar", "application/octet-stream"},
        {0, 0}
};

/*
 A helper function
 */
void error(const char *msg) {
        perror(msg);
        exit(1);
}

/*
 * 获得文件大小
 */
int get_file_size(int fd) {
        struct stat stat_struct;
        if (fstat(fd, &stat_struct) == -1)
                return (1);
        return (int) stat_struct.st_size;
}

/*
 相应数据send
 */
void send_new(int fd, char *msg) {
        int len = strlen(msg);
        if (send(fd, msg, len, 0) == -1) {
                printf("Error in send\n");
        }
}

/*
 This function recieves the buffer
 until an "End of line(EOL)" byte is recieved
 */
int recv_new(int fd, char *buffer) {
        char *p = buffer; // Use of a pointer to the buffer rather than dealing with the buffer directly
        int eol_matched = 0; // Use to check whether the recieved byte is matched with the buffer byte or not
        while (recv(fd, p, 1, 0) != 0) // Start receiving 1 byte at a time
        {
                if (*p == EOL[eol_matched]) // if the byte matches with the first eol byte that is '\r'
                {
                        ++eol_matched;
                        if (eol_matched == EOL_SIZE) // if both the bytes matches with the EOL
                        {
                                *(p + 1 - EOL_SIZE) = '\0'; // End the string
                                return (strlen(buffer)); // Return the bytes recieved
                        }
                } else {
                        eol_matched = 0;
                }
                p++; // Increment the pointer to receive next byte
        }
        return (0);
}

/*
站点根目录
 */
char* webroot() {
        // open the file "conf" for reading
        //FILE *in = fopen("conf", "rt");
        // read the first line from the file
        // char buff[1000];
        //fgets(buff, 1000, in);
        // close the stream
        /*
            fclose(in);
            char* nl_ptr = strrchr(buff, '\n');
            if (nl_ptr != NULL)
         *nl_ptr = '\0';
            return strdup(buff);
         */
        return "/home/xiao/work/www-data";
}

/*
处理php请求
 */
void php_cgi(char* script_path, char* query_string, int fd) {
        send_new(fd, "HTTP/1.1 200 OK\n Server: Web Server in C\n Connection: close\n");
        dup2(fd, STDOUT_FILENO);
        char script[500];
        strcpy(script, "SCRIPT_FILENAME=");
        strcat(script, script_path);
        putenv("GATEWAY_INTERFACE=CGI/1.1");
        putenv(script);
        char *query_str;
        query_str = (char*)malloc(sizeof(char));
        sprintf(query_str,"QUERY_STRING=%s",query_string);
        putenv(query_str);
        putenv("REQUEST_METHOD=GET");
        putenv("REDIRECT_STATUS=true");
        putenv("SERVER_PROTOCOL=HTTP/1.1");
        putenv("REMOTE_HOST=127.0.0.1");
        execl("/data/service/php54/bin/php-cgi", "php-cgi", NULL);
        free(query_str);
}

/*
处理链接句柄
 */
int connection(int fd) {
        char request[500], resource[500], *ptr;
        int fd1, length;
        if (recv_new(fd, request) == 0) {
                printf("Recieve Failed\n");
        }
        printf("%s sssaaa\n", request);

        // Check for a valid browser request
        ptr = strstr(request, " HTTP/");
        if (ptr == NULL) {
                printf("NOT HTTP !\n");
        } else {
                *ptr = 0;
                ptr = NULL;
                if (strncmp(request, "GET ", 4) == 0) {
                        ptr = request + 4;
                }
                if (ptr == NULL) {
                        printf("Unknown Request ! \n");
                } else {
                        if (ptr[strlen(ptr) - 1] == '/') {
                                strcat(ptr, "index.html");
                        }
                        strcpy(resource, webroot());

                        //网站根目录+请求路径，拼接
                        strcat(resource, ptr);
                        //
                        char* s = strchr(ptr, '.');
                        char *query_str = strchr(ptr, '?');
                        //int suffix_port = strcspn(ptr, '.');
                        printf("Get eeeeee==== %s\n", s);
                        int param_port, request_len;
                        param_port = strcspn(ptr, "?");
                        request_len = strlen(ptr);
                        int i;
                        printf("Get ddddddssss==== %d\n", param_port);
                        //
                        for (i = 0; extensions[i].ext != NULL; i++) {
                                printf("Get dddddd==== %d\n", request_len);
                                char resource_file[500];
                                strcpy(resource_file, webroot());
                                char *request_sufix;
                                request_sufix = (char*) malloc(sizeof (char));
                                if (param_port > 0 && request_len > param_port) {
                                        strncpy(request_sufix, ptr, param_port);
                                        strcat(resource_file, request_sufix);
                                        s = strchr(request_sufix, '.');
                                        printf("Get aaaaa==== %s\n", s);
                                } else {
                                        strcat(resource_file, ptr);
                                }
                                printf("Get bbbbb==== %s\n", resource_file);
                                if (strcmp(s + 1, extensions[i].ext) == 0) {
                                        fd1 = open(resource_file, O_RDONLY, 0); //打开请求的文件
                                        printf("Opening \"%s\"\n", resource_file);
                                        if (fd1 == -1) {
                                                printf("404 File not found Error\n");
                                                send_new(fd, "HTTP/1.1 404 Not Found\r\n");
                                                send_new(fd, "Server : Web Server in C\r\n\r\n");
                                                send_new(fd, "<html><head><title>404 Not Found</head></title>");
                                                send_new(fd, "<body><p>404 Not Found: The requested resource could not be found!</p></body></html>");
                                                //Handling php requests
                                        } else if (strcmp(extensions[i].ext, "php") == 0) //如果请求的是php文件
                                        {
                                                php_cgi(resource_file,query_str, fd);
                                                //sleep();
                                                //break;
                                        } else {
                                                printf("200 OK, Content-Type: %s\n\n", extensions[i].mediatype);


                                                //                        char *http_status;
                                                //                        char *content_type;
                                                //                        char *http_server;
                                                //                        sprintf(http_status,"HTTP/1.1 200 OK\r\n");
                                                //                        sprintf(content_type,"Content-Type: %s\r\n",extensions[i].mediatype);
                                                //                        sprintf(http_server,"Server : Web Server in C\r\n\r\n");
                                                //                        send_new(fd, http_status);
                                                //                        send_new(fd, content_type);
                                                //                        send_new(fd, http_server);

                                                if (ptr == request + 4) // if it is a GET request
                                                {
                                                        if ((length = get_file_size(fd1)) == -1)
                                                                printf("Error in getting size !\n");

                                                        send_new(fd, "HTTP/1.0 200 OK \r\n");
                                                        send_new(fd, "Connection: keep-alive \r\n");

                                                        //相应的流数据类型
                                                        char *content_type;
                                                        content_type = (char*) malloc(sizeof (char));
                                                        sprintf(content_type, "Content-Type: %s \r\n", extensions[i].mediatype);
                                                        send_new(fd, content_type);


                                                        send_new(fd, "Cache-Control: max-age=31536000 \r\n");

                                                        char *content_length;
                                                        content_length = (char*) malloc(sizeof (char));
                                                        sprintf(content_length, "Content-length: %d \r\n", length);
                                                        send_new(fd, content_length);

                                                        send_new(fd, "Power by: totogoo \r\n");
                                                        send_new(fd, "Server: nginx \r\n\r\n");

                                                        size_t total_bytes_sent = 0;
                                                        ssize_t bytes_sent;
                                                        while (total_bytes_sent < length) {
                                                                //Zero copy optimization
                                                                if ((bytes_sent = sendfile(fd, fd1, 0, length - total_bytes_sent)) <= 0) {
                                                                        if (errno == EINTR || errno == EAGAIN) {
                                                                                continue;
                                                                        }
                                                                        perror("sendfile");
                                                                        return -1;
                                                                }
                                                                total_bytes_sent += bytes_sent;
                                                        }
                                                        free(content_type);
                                                        free(content_length);
                                                }
                                        }
                                         free(request_sufix);
                                        break;
                                }
                                 free(request_sufix);
                                int size = sizeof (extensions) / sizeof (extensions[0]);
                                if (i == size - 2) {
                                        printf("415 Unsupported Media Type\n");
                                        send_new(fd, "HTTP/1.1 415 Unsupported Media Type\r\n");
                                        send_new(fd, "Server : Web Server in C\r\n\r\n");
                                        send_new(fd, "<html><head><title>415 Unsupported Media Type</head></title>");
                                        send_new(fd, "<body><p>415 Unsupported Media Type!</p></body></html>");
                                }
                        }
                        close(fd);
                }
        }
        shutdown(fd, SHUT_RDWR);
}

int main(int argc, char *argv[]) {
        int sockfd, newsockfd, portno, pid;
        socklen_t clilen;
        struct sockaddr_in serv_addr, cli_addr;

        if (argc < 2) {
                portno = 8300;
        } else {
                portno = atoi(argv[1]);
        }
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
                error("ERROR opening socket");
        bzero((char *) &serv_addr, sizeof (serv_addr));

        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = INADDR_ANY;
        serv_addr.sin_port = htons(portno);
        if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0)
                error("ERROR on binding");
        listen(sockfd, 5);
        clilen = sizeof (cli_addr);
        /*
        主进进程监听链接请求，对每一个新的链接请求fork一个单独的子进程进行处理
         */
        while (1) {
                newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
                if (newsockfd < 0)
                        error("ERROR on accept");
                pid = fork();
                if (pid < 0)
                        error("ERROR on fork");
                if (pid == 0) {
                        //子进程
                        printf("pid %d",pid);
                        close(sockfd);
                        connection(newsockfd);
                        exit(EXIT_SUCCESS);
                } else
                        //父进程
                        printf("pid %d",pid);
                        close(newsockfd);
        }
        close(sockfd);
        return 0;
}